﻿using System;
using System.Collections.Generic;
using System.Windows.Media;
using Box2DX.Collision;
using Box2DX.Common;
using Box2DX.Dynamics;

namespace Expression.Samples.Physics
{
	public class Simulator
	{
		private float interval;
		private World world;
		private List<Entity> entities;
		private SimulatorContext context;
		private SimpleContactListener contactListener;
		private PhysicsContext physicsContext;

		public float Interval
		{
			get { return this.interval; }
		}

		public Vector2 WorldSize
		{
			get;
			set;
		}

		public SimulatorContext SimulatorContext
		{
			get { return this.context; }
		}

		public World World
		{
			get { return this.world; }
		}

		public bool IsRunning { get; private set; }

		public List<Entity> Entities
		{
			get
			{
				if (this.entities == null)
				{
					this.entities = new List<Entity>();
				}
				return this.entities;
			}
		}

		public PhysicsContext PhysicsContext {
			get { return this.physicsContext; }
		}

		public Simulator(PhysicsContext physicsContext, Vector2 screenTopLeft, Vector2 screenBottomRight, Vector2 worldTopLeft, Vector2 worldBottomRight, float gravity, float interval)
		{
			this.physicsContext = physicsContext;
			this.context = new SimulatorContext(screenTopLeft, screenBottomRight, worldTopLeft, worldBottomRight);
			AABB worldAABB = new AABB();
			worldAABB.LowerBound = new Vector2(-200, -200);
			worldAABB.UpperBound = new Vector2(200, 200);
			this.world = new World(worldAABB, new Vector2(0, gravity), true);
			this.contactListener = new SimpleContactListener();
			this.world.SetContactListener(this.contactListener);
			this.interval = interval;
			this.World.SetBoundaryListener(new SimpleBoundaryListener(this.HandleBoundaryViolation));
		}

		public void AddEntity(Entity entity, BodyParameters bodyParameters) {
			entity.Initialize(this.physicsContext, bodyParameters);
			this.Entities.Add(entity);
		}

		public void RemoveEntity(Entity entity) {
			if (entity.Body != null)
				this.World.DestroyBody(entity.Body);

			this.Entities.Remove(entity);
		}

		public void Start()
		{
			this.IsRunning = true;
			CompositionTarget.Rendering += this.OnTimerElapsed;
			this.lastTime = DateTime.Now;
			
		}

		void OnTimerElapsed(object sender, EventArgs e) {
			this.Update();
		}


		float totalElapsedTime = 0;
		DateTime lastTime = DateTime.MinValue;
		float accumulator = 0;

		public void Update()
		{
			DateTime currentTime = DateTime.Now;
			TimeSpan elapsedTime = currentTime - this.lastTime;
			this.lastTime = currentTime;
			float elapsed = (float)elapsedTime.TotalMilliseconds;
			accumulator += elapsed;

			while (accumulator > this.interval)
			{
				this.World.Step(this.interval * 0.001f, 10);
				totalElapsedTime += this.interval;
				accumulator -= this.interval;
			}

			this.contactListener.ProcessContacts();

			foreach (Entity entity in this.Entities) {
				if (entity.Body != null) {
					Vector2 blockPosition = entity.BodyPosition;
					double rotation = entity.BodyAngle;

					if (blockPosition.IsValid && !entity.Body.IsStatic) {
						// TODO fix this; block position is centered, element position isn't
						Vector2 position = blockPosition - (entity.BodySize * 0.5f);
						entity.ElementPosition = entity.Simulator.SimulatorContext.WorldToScreen(position);
						entity.ElementAngle = rotation;
					}
				}
			}
		}

		public void Stop()
		{
			this.IsRunning = false;
			CompositionTarget.Rendering -= this.OnTimerElapsed;
		}

		private void HandleBoundaryViolation(Body body) {
			Entity entity = body.UserData as Entity;
			if (entity != null)
				entity.OnBoundaryViolation();
		}
	}
}
